#include "Configurator.h"

/* ------------------------------- backgrounds ------------------------------ */
#include "Background/SkySphereBackground.h"
#include "Background/SolidColorBackground.h"

/* --------------------------------- cameras -------------------------------- */
#include "Camera/OrthographicCamera.h"
#include "Camera/PerspectiveCamera.h"
#include "Camera/SkySphereCamera.h"

/* --------------------------------- lights --------------------------------- */
#include "Light/ParallelLight.h"
#include "Light/PlaneMeshlight.h"
#include "Light/PointLight.h"

/* -------------------------------- materials ------------------------------- */
#include "Material/DaviesMaterial.h"
#include "Material/GGXMaterial.h"
#include "Material/GlassMaterial.h"
#include "Material/LambertianMaterial.h"
#include "Material/MirrorMaterial.h"
#include "Material/MixedMaterial.h"

/* --------------------------------- mediums -------------------------------- */
#include "Medium/HomogeneousMedium.h"

/* --------------------------------- meshes --------------------------------- */
#include "Mesh/PlaneMesh.h"
#include "Mesh/TriangleMesh.h"

/* ------------------------------- integrators ------------------------------ */
#include "Integrator/DebugIntegrator.h"
#include "Integrator/PathIntegrator.h"
#include "Integrator/VolPathIntegrator.h"

/* ----------------------------------- IAS ---------------------------------- */
#include "core/Animation.h"
#include "core/Instance.h"

using namespace OBR;

Configurator::Configurator()
    : desc_file_loc(std::string(PROJ_ROOT) + "/config/SceneDescription.json")
{
}

Configurator::Configurator(std::string desc_path) : desc_file_loc(desc_path)
{
}

Configurator::~Configurator()
{
}

void Configurator::initProgram()
{
    // register builders
    /* ----------------------------- backgrounds ---------------------------- */
    BackgroundFactory::registerClass("solid color", builder<Background, SolidColorBackground>);
    BackgroundFactory::registerClass("sphere", builder<Background, SkySphereBackground>);
    /* ------------------------------- cameras ------------------------------ */
    CameraFactory::registerClass("orthographic", builder<Camera, OrthographicCamera>);
    CameraFactory::registerClass("perspective", builder<Camera, PerspectiveCamera>);
    CameraFactory::registerClass("sphere", builder<Camera, SkySphereCamera>);
    /* ------------------------------- lights ------------------------------- */
    LightFactory::registerClass("point", builder<Light, PointLight>);
    LightFactory::registerClass("parallel", builder<Light, ParallelLight>);
    LightFactory::registerClass("plane mesh", builder<Light, PlaneMeshLight>);
    /* ------------------------------ materials ----------------------------- */
    MaterialFactory::registerClass("lambertian", builder<Material, LambertianMaterial>);
    MaterialFactory::registerClass("mirror", builder<Material, MirrorMaterial>);
    MaterialFactory::registerClass("glass", builder<Material, GlassMaterial>);
    MaterialFactory::registerClass("ggx", builder<Material, GGXMaterial>);
    MaterialFactory::registerClass("davies", builder<Material, DaviesMaterial>);
    MaterialFactory::registerClass("mixed", builder<Material, MixedMaterial>);
    /* ------------------------------- mediums ------------------------------ */
    MediumFactory::registerClass("homogeneous", builder<Medium, HomogeneousMedium>);
    /* ------------------------------- meshes ------------------------------- */
    MeshFactory::registerClass("triangle", builder<Mesh, TriangleMesh>);
    MeshFactory::registerClass("plane", builder<Mesh, PlaneMesh>);
    /* ----------------------------- integrators ---------------------------- */
    IntergratorFactory::registerClass("debug", builder<Integrator, DebugIntegrator>);
    IntergratorFactory::registerClass("path", builder<Integrator, PathIntegrator>);
    IntergratorFactory::registerClass("volpath", builder<Integrator, VolPathIntegrator>);
    /* ------------------------------ instances ----------------------------- */
    InstanceFactory::registerClass("static", builder<Instance, StaticInstance>);
    InstanceFactory::registerClass("matrix animation", builder<Instance, MatrixAnimation>);
    InstanceFactory::registerClass("srt animation", builder<Instance, SRTAnimation>);
}

Scene Configurator::getScene()
{
    Json::Value config;
    Json::Reader reader;

    bool reached_true_config = false;
    while (!reached_true_config)
    {
        std::ifstream conf_fs(this->desc_file_loc);

        // check file stream
        if (!conf_fs)
        {
            std::cerr << "Configurator: error opening file @ " << this->desc_file_loc << std::endl;
            throw std::runtime_error("Configurator: error opening file");
        }

        // parse file
        if (!reader.parse(conf_fs, config))
        {
            std::cerr << "Configurator: error parsing file @ " << this->desc_file_loc << std::endl;
            throw std::runtime_error("Configurator: error opening file");
        }

        if (config.isMember("redirect"))
        {
            this->desc_file_loc = config["redirect"].asString();
        }
        else
        {
            reached_true_config = true;
        }

        conf_fs.close();
    }

    // create scene
    Scene scene;
    /* ------------------------------ get materials ----------------------------- */
    for (Json::ArrayIndex i = 0; i < config["materials"].size(); i++)
    {
        Json::Value mat_conf = config["materials"][i];
        BuildResult<Material> result =
            MaterialFactory::fromJsonConfig(mat_conf["type"].asString(), mat_conf);
        std::invoke(result.callback, result.object, scene);
        scene.materials.addInstance(result.object);
    }
    /* ------------------------------- get mediums ------------------------------- */
    for (Json::ArrayIndex i = 0; i < config["mediums"].size(); i++)
    {
        Json::Value med_conf = config["mediums"][i];
        BuildResult<Medium> result =
            MediumFactory::fromJsonConfig(med_conf["type"].asString(), med_conf);
        std::invoke(result.callback, result.object, scene);
        scene.mediums.addInstance(result.object);
    }
    /* ------------------------------- get meshes ------------------------------- */
    for (Json::ArrayIndex i = 0; i < config["meshes"].size(); i++)
    {
        Json::Value mesh_conf = config["meshes"][i];
        BuildResult<Mesh> result =
            MeshFactory::fromJsonConfig(mesh_conf["type"].asString(), mesh_conf);
        std::invoke(result.callback, result.object, scene);
        scene.meshes.addInstance(result.object);
    }
    /* ------------------------------- get lights ------------------------------- */
    for (Json::ArrayIndex i = 0; i < config["lights"].size(); i++)
    {
        Json::Value light_conf = config["lights"][i];
        BuildResult<Light> result =
            LightFactory::fromJsonConfig(light_conf["type"].asString(), light_conf);
        std::invoke(result.callback, result.object, scene);
        scene.lights.addInstance(result.object);
    }
    /* -------------------------- set AS structure -------------------------- */
    for (Json::ArrayIndex i = 0; i < config["instances"].size(); i++)
    {
        Json::Value inst_conf = config["instances"][i];
        BuildResult<Instance> result =
            InstanceFactory::fromJsonConfig(inst_conf["type"].asString(), inst_conf);
        std::invoke(result.callback, result.object, scene);
        scene.instances.push_back(result.object);
    }
    scene.rootAS = scene.instances[scene.instances.size() - 1];
    // update local to world matrix to sbt level
    scene.rootAS->updateLocalToWorld(Matrix4x4f(1.0f));
    /* ----------------------------- get background ----------------------------- */
    {
        Json::Value bg_conf = config["background"];
        BuildResult<Background> result =
            BackgroundFactory::fromJsonConfig(bg_conf["type"].asString(), bg_conf);
        std::invoke(result.callback, result.object, scene);
        scene.setBackground(result.object);
    }
    /* ------------------------------- get camera ------------------------------- */
    {
        Json::Value cam_conf = config["camera"];
        BuildResult<Camera> result =
            CameraFactory::fromJsonConfig(cam_conf["type"].asString(), cam_conf);
        // link medium to camera
        result.object->linkMedium(scene.mediums); // TODO: move to callback
        std::invoke(result.callback, result.object, scene);
        scene.setCamera(result.object);
    }
    /* --------------------------- get integrator --------------------------- */
    {
        Json::Value int_conf = config["integrator"];
        BuildResult<Integrator> result =
            IntergratorFactory::fromJsonConfig(int_conf["type"].asString(), int_conf);
        std::invoke(result.callback, result.object, scene);
        scene.setIntegrator(result.object);
    }

    /* --------------------------- init launch params --------------------------- */
    scene.params.samples_per_launch = config["renderer"]["spp"].asInt();
    scene.params.tmin = config["renderer"]["tmin"].asInt();
    scene.params.tmax = config["renderer"]["tmax"].asInt();
    scene.params.width = config["renderer"]["width"].asInt();
    scene.params.height = config["renderer"]["height"].asInt();
    scene.params.num_fbos = scene.integrator->num_fbos;
    scene.params.num_total_lights = scene.lights.getInstanceCount();
    scene.params.fbo = nullptr;

    scene.target_spp = config["renderer"].isMember("target spp")
                           ? config["renderer"]["target spp"].asInt()
                           : scene.params.samples_per_launch;

    return scene;
}